﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows.Threading;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;

namespace SettlersEngine
{
    internal delegate void PlayerStateHandler(AnimationPlayer inPlayer);

    internal class AnimationPlayer
    {
        private IAddChild m_Container = null;
        private Double m_Position;
        private Int64 m_PlayStartMillis = 0;
        private Int64 m_PlayPauseMillis = 0;
        private LinkedListNode<AnimationPlayer> m_Node = null;
        private AnimationSurface m_Surface = null;
        private Double m_LastRepeatPosition = 0;

        private static LinkedList<AnimationPlayer> m_AttachedPlayers = new LinkedList<AnimationPlayer>();
        //private static System.Threading.Timer m_NativeTimer = new System.Threading.Timer(OnAnimationTimer, null, 30, 30);
        private static DispatcherTimer m_WPFTimer = new DispatcherTimer();
        internal static Int64 ElapsedMillis { get { return WPFPrecisionSchedule.ElapsedMillis; } }
        public AnimationClass Class { get; private set; }
        public AnimationSet Current
        {
            get
            {
                if (m_Surface == null)
                    return null;

                return m_Surface.CurrentSet;
            }
        }

        /// <summary>
        /// The getter will always return a value between 0.0 and 1.0. Instead the Setter will take
        /// any value and automatically convert it into the expected range. 
        /// </summary>
        public Double Position
        {
            get { return m_Position; }
        }

        public Boolean Repeat { get; set; }
        public Boolean IsPlaying { get; private set; }

        public IAddChild Container
        {
            get { return m_Container; }
            set { Attach(value); }
        }

        public event PlayerStateHandler OnPlay;
        public event PlayerStateHandler OnStop;
        public event PlayerStateHandler OnRepeat;

        static AnimationPlayer()
        {
            m_WPFTimer.Interval = TimeSpan.FromMilliseconds(30);
            m_WPFTimer.Tick += new EventHandler(m_WPFTimer_Tick);
            m_WPFTimer.Start();
        }

        static void m_WPFTimer_Tick(object sender, EventArgs e)
        {
            OnAnimationTimer(null);
        }

        private static void OnAnimationTimer(object unused)
        {
            Int64 elapsed = ElapsedMillis;

            lock (m_AttachedPlayers)
            {
                foreach (var player in m_AttachedPlayers)
                {
                    player.Update(elapsed);
                }
            }
        }

        /// <summary>
        /// Updates all animation frame indices to match the current animation position.
        /// This method is thread safe by design and does not commit any changes to WPF.
        /// Another WPF thread has to synchronize the UI with new animation values.
        /// </summary>
        protected void Update(Int64 inAbsoluteMillis)
        {
            Int64 elapsed = inAbsoluteMillis - m_PlayStartMillis;

            if(m_Surface.AmbientOrEmptyVisual != null)
                UpdateVisual(elapsed, m_Surface.AmbientOrEmptyVisual, true);

            if ((Current == null) || !IsPlaying)
                return;

            if (m_Surface.CurrentVisual != null)
                UpdateVisual(elapsed, m_Surface.CurrentVisual, false);
        }

        private void UpdateVisual(
            Int64 elapsed,
            AnimationSetVisual visual,
            Boolean isAmbientOrEmptySet)
        {
            Double newPos = 0;

            if (visual.Set.DurationMillis > 0)
            {
                newPos = elapsed / (Double)visual.Set.DurationMillis;

                if ((newPos >= m_LastRepeatPosition + 1.0) && !isAmbientOrEmptySet)
                {
                    if (!Repeat)
                    {
                        Stop();

                        return;
                    }

                    m_LastRepeatPosition = Math.Floor(newPos);

                    if (OnRepeat != null)
                        OnRepeat(this);
                }
                    
                m_Position = Math.IEEERemainder(newPos, 1.0);

                if (m_Position < 0)
                    m_Position = 1.0 + m_Position;
            }
            else
                m_Position = 0;
            /*
            if (isAmbientOrEmptySet)
            {
                if (visual.Set.Class.UseNoAmbientOrEmptySet || (visual.Set.Class.UseEmptySet && (m_Surface.CurrentVisual != null)))
                {
                    foreach (var img in visual.Images)
                    {
                        img.Source = null;
                    }

                    return;
                }
            }*/

            for (int i = 0; i < visual.Set.Animations.Count; i++)
            {
                Animation anim = visual.Set.Animations[i];
                Image img = visual.Images[i];
                AnimationFrame frame = null;

                if (anim.IsFrozen)
                {
                    if (anim.FrozenFrame != null)
                        frame = anim.FrozenFrame;
                }
                else if (anim.Frames.Count > 0)
                    frame = anim.Frames[(int)(anim.Frames.Count * m_Position)];

                if (frame != null)
                {
                    img.Margin = new Thickness(frame.OffsetX + anim.OffsetX, frame.OffsetY + anim.OffsetY, 0, 0);
                    img.Source = frame.Source;
                }
                else
                    img.Source = null;
            }
        }

        public AnimationPlayer(AnimationClass inClass)
        {
            if (inClass == null)
                throw new ArgumentNullException();

            Class = inClass;
            OnRepeat += SoundRepeatHandler;
        }

        public void Attach(IAddChild inContainer)
        {
            if (!(inContainer is Panel) && !(inContainer is ContentControl))
                throw new ApplicationException("An animation container has to be a subclass of System.Windows.Controls.Panel or System.Windows.Controls.ContentControl!");

            if (m_Container != null)
                Detach();

            m_Container = inContainer;
            m_Surface = new AnimationSurface(Class);

            if (m_Container != null)
            {
                inContainer.AddChild(m_Surface);
            }

            lock (m_AttachedPlayers)
            {
                m_Node = m_AttachedPlayers.AddLast(this);
            }
        }

        public void Detach()
        {
            if (m_Container == null)
                return;

            if (m_Container is Panel)
            {
                Panel panel = (Panel)m_Container;

                if (!panel.Children.Contains(m_Surface))
                    throw new ApplicationException("Container does not contain the required animation surface being detached.");

                panel.Children.Remove(m_Surface);
            }
            else if (m_Container is ContentControl)
            {
                ContentControl ctrl = (ContentControl)m_Container;

                if(ctrl.Content != m_Surface)
                    throw new ApplicationException("Content control does not contain the required animation surface being detached.");

                ctrl.Content = null;
            }

            m_Container = null;
            m_Surface = null;

            lock (m_AttachedPlayers)
            {
                m_AttachedPlayers.Remove(m_Node);
                m_Node = null;
            }
        }

        ~AnimationPlayer()
        {
            Stop();
        }

        public void Play(AnimationSet inSet)
        {
            if (inSet == null)
                throw new ArgumentNullException();

            m_Surface.CurrentSet = inSet;
            IsPlaying = true;
            m_PlayStartMillis = ElapsedMillis;

            PlaySounds();

            if (OnPlay != null)
                OnPlay(this);
        }

        public void Stop()
        {
            IsPlaying = false;
            m_PlayStartMillis = ElapsedMillis;

            StopSounds();

            if (OnStop != null)
                OnStop(this);
        }

        private class SoundRepeatCallback
        {
            public ISoundPlayer Player;
            public Int64 Interval;
        }

        private void SoundRepeatHandler(AnimationPlayer inPlayer)
        {
            if (m_Surface.CurrentVisual == null)
                return;

            foreach (ISoundPlayer sound in m_Surface.CurrentVisual.Sounds)
            {
                if (sound.Animation.RepeatSound)
                    continue; // will be setup in PlayVisualSounds()

                if (!sound.Animation.PlaySound)
                    continue;

                sound.Stop();
                sound.Play(sound.Animation.SoundStartDelay);
            }
        }

        private void SoundRepeatHandler(Int64 inElapsed, Object inCallback)
        {
            if (!IsPlaying)
                return;

            SoundRepeatCallback callback = (SoundRepeatCallback)inCallback;

            callback.Player.Stop();

            if (callback.Player.Animation.PlaySound)
                callback.Player.Play(0);

            WPFPrecisionSchedule.QueueTask(SoundRepeatHandler, callback.Interval, callback);
        }

        private void PlaySounds()
        {
            if ((m_Surface == null) || (m_Surface.CurrentVisual == null))
                return;

            StopSounds();

            foreach (Animation anim in m_Surface.CurrentVisual.Set.Animations)
            {
                ISoundPlayer player;

                if (!anim.PlaySound || (anim.Sound == null))
                    continue;

                m_Surface.CurrentVisual.Sounds.Add(player = anim.Sound.CreatePlayer(anim));

                if (anim.RepeatSound)
                {
                    WPFPrecisionSchedule.QueueTask((a, b) =>
                    {
                        SoundRepeatHandler(0, new SoundRepeatCallback()
                        {
                            Interval = (long)(anim.Sound.Duration.TotalMilliseconds + anim.SoundRepeatDelay),
                            Player = player,
                        });
                    }, anim.SoundStartDelay);
                }
                else
                {
                    player.Play(anim.SoundStartDelay);
                }
            }
        }

        private void StopSounds()
        {
            if ((m_Surface == null) || (m_Surface.CurrentVisual == null))
                return;

            foreach (ISoundPlayer sound in m_Surface.CurrentVisual.Sounds)
            {
                sound.Stop();
            }

            m_Surface.CurrentVisual.Sounds.Clear();
        }

        public void SetFrame(Animation inAnimation, Int32 inFrameIndex)
        {
        }
    }
}
